//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.eResearch.Blackboard
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Configuration;
    using System.IO;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.Text;
    using System.Threading;
    using Microsoft.Research.eResearch.Blackboard.Interfaces;
    using Microsoft.Research.eResearch.Common.Threading;

    /// <summary>
    /// Provides a storage implementation of IPublisherStore.  A list of current subscribers is 
    /// maintained and binary-serialized to disk whenever publishers are registered and unregistered.
    /// </summary>
    public class FilePublisherStore : IPublisherStore
    {
        /// <summary>
        /// Name of the publisher store token.
        /// </summary>
        private const string PublisherStoreToken = "PublisherStore";

        /// <summary>
        /// Name of the file name.
        /// </summary>
        private const string DefaultFileName = "PublisherStore.bin";

        /// <summary>
        /// Instance of the write lock.
        /// </summary>
        private ReaderWriterLockSlim publishersLock = new ReaderWriterLockSlim();

        /// <summary>
        /// List of publishers.
        /// </summary>
        private List<Uri> publishers;

        /// <summary>
        /// File name where the publisher information is stored.
        /// </summary>
        private string fileName;

        /// <summary>
        /// Initializes a new instance of the <see cref="FilePublisherStore"/> class.
        /// The name of the file to use is loaded from the appSettings section of the config file
        /// with the key "PublisherStore".  If the file specified by this key/value pair does not
        /// exist, then a new one is created.
        /// </summary>
        public FilePublisherStore()
        {
            fileName = ConfigurationManager.AppSettings[PublisherStoreToken];

            if (String.IsNullOrEmpty(fileName))
            {
                fileName = DefaultFileName;
                ConfigurationManager.AppSettings[PublisherStoreToken] = DefaultFileName;
            }

            LoadPublisherStoreFromDisk();
        }

        /// <summary>
        /// Gets the filename where the PublisherStore will be serialized.
        /// </summary>
        public string FileName
        {
            get { return fileName; }
        }

        /// <summary>
        /// Registers the specified return address into the list of current publishers.
        /// </summary>
        /// <param name="returnAddress">The return address of the new publisher.</param>
        public void Register(Uri returnAddress)
        {
            using (new AcquireWriterLock(publishersLock))
            {
                if (!publishers.Contains(returnAddress))
                {
                    publishers.Add(returnAddress);
                    WritePublisherStoreToDisk();
                }
            }
        } 

        /// <summary>
        /// Unregisters the specified return address from the list of current publishers.
        /// </summary>
        /// <param name="returnAddress">The return address to unregister.</param>
        public void Unregister(Uri returnAddress)
        {
            using (new AcquireWriterLock(publishersLock))
            {
                if (publishers.Contains(returnAddress))
                {
                    publishers.Remove(returnAddress);
                    WritePublisherStoreToDisk();
                }
            }
        } 

        /// <summary>
        /// Gets the list of current publishers.
        /// </summary>
        /// <returns>A collection of current publishers.</returns>
        public ReadOnlyCollection<Uri> GetCurrentPublisherList()
        {
            ReadOnlyCollection<Uri> copy;

            // Note: The sequence of events is as follows: Reader lock acquired, list is read, lock is released, method returns
            using (new AcquireReaderLock(publishersLock))
            {
                copy = (new List<Uri>(publishers)).AsReadOnly();
            }

            return copy;
        } 

        /// <summary>
        /// Writes the publisher store to disk.
        /// </summary>
        private void WritePublisherStoreToDisk()
        {
            try
            {
                using (Stream fs = new FileStream(fileName, FileMode.Create))
                {
                    IFormatter formatter = new BinaryFormatter();
                    formatter.Serialize(fs, publishers);
                }
            }
            catch (Exception)
            {
                // Nothing we can really do, so just eat the exception.
                // Everything will still work since the data is also in memory.
            }
        } 

        /// <summary>
        /// Loads the publisher store from disk.
        /// </summary>
        private void LoadPublisherStoreFromDisk()
        {
            try
            {
                using (Stream fs = new FileStream(fileName, FileMode.Open))
                {
                    IFormatter formatter = new BinaryFormatter();
                    publishers = (List<Uri>)formatter.Deserialize(fs);
                }
            }
            catch (Exception)
            {
                publishers = new List<Uri>();
                WritePublisherStoreToDisk();
            }
        } 
    }
}
